iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0

資料庫遷移是以執行一個個檔案來逐步建立資料庫表單的作法,可以紀錄資料庫變化的過程。逐步變更可以降低對已上線系統的影響,也能在出錯的時候退回到還能正常運作時的資料庫結構。

可以先來看看 Laravel 預先建立好的 Migration 檔案

// database\migrations\2014_10_12_000000_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

這是用來建立 User 表單的遷移,首先可以看到主要分成了 up / down 兩個功能, up 就是推進遷移時執行, down 則是退回遷移步驟時執行。

up 不一定是建立或增加欄位,可能會是刪除欄位,變更欄位屬性(移除 Foreign key 等),或是刪除表單也有可能,而 down 中需要對應 up 的內容執行相反的指令。

如果還沒跑過可以先跑一次 Migration 建立使用者登入相關的表單

sail artisan migrate

成功的話可以在資料庫看到新建的表單

建立 Migration

用指令建立 Migration 檔案

sail artisan make:migration <migratoin_name>

這樣建出來的 Migration 會放在 database/migrations 目錄下,並且 up / down 方法都是空的。

如果 Migration 是用來建立表單的話,可以在指令中指定要建立的表單名稱

 sail artisan make:migration create_todos_table --create todos

這樣建立的 Migration 中就會預設好建立表單的指令, down 也會填好刪除表單的指令。

// database\migrations\XXXX_XX_XX_XXXXX_create_todos_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTodosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('todo');
    }
}

另外說一下 Migration 檔案都會在檔名前面加上時間戳,以辨別 Migration 的執行順序。

如果不是想建立表單而是變更某個表單的內容的話,指令會變成

sail artisan make:migration update_todos_table --table todos

另外如果想指定 migrations 建立的位置,指令可以加上 path 參數

sail artisan make:migration update_todos_table  --table todos --path database/migrations/Todos

path 會是相對於專案目錄的位置

Migration 建立表單

如果是在 Migration 中增改刪除表單,都是利用 Schema 這個幫手。

Schema::create() 方法會建立新的表單

public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->id();
        $table->timestamps();
    });
}

up() 裡是建立表單的話,down() 裡就會寫刪除表單的方法

public function down()
{
    Schema::dropIfExists('todos');
}

關於表單的名稱,一般會加上 's' ,這樣之後建立 Eloquent ORM 的模型時,就不用在特地宣告模型對應的表單,Eloquent ORM 會自動對應,例如 Flight 模型會自動找 flights 表單進行查詢。

當然如果名稱對不上的話,手動宣告模型對應的表單就好。

Migration 建立欄位

建立表單的同時,可以宣告表單要有的欄位。

欄位的定義包含名稱、型別、是否可為空等,這邊列一些比較常用的。

$table->uuid('id'); // id -> 第一個參數是欄位名稱,其他方法也一樣

建立值為 UUID 的欄位,通常用來當主鍵,或宣告外鍵欄位。

$table->boolean('confirmed');
$table->integer('votes');
$table->float('amount', 3, 2); // 3->數字總數 , 2->小數位數 ,例: 1.23
$table->string('name', 100); //100->字數上限
$table->longText('description');
$table->dateTime('created_at');

常用型別以及日期時間型別

$table->json('options');

可以把大量資料包成 json 存起來,要用的時候再解析,我們這通常用來保存機器的通訊數據原始資料。

$table->timestamps();

一次建立常用的 created_at 跟 updated_at 欄位。

$table->string('description')->nullable();

nullable 宣告該欄位可為空值,沒有宣告 nullable 的話當欄位沒值會報錯。

$table->bool('receive_ads')->default(true);

當未宣告欄位值的話自動填入預設的值,就不用宣告 nullable 了。

$table->string('email')->unique();

宣告必須為獨特值的欄位,該欄位內的所有值必須是獨一無二的,寫入時若發現有重複會報錯。

宣告關聯

建立表單時也可以加上表單間的關聯性,通常是宣告一個外鍵後綁定到其他表單的主鍵上。

$table->uuid('user_id');

$table->foreign('user_id')->references('id')->on('users');

要注意的是當宣告關連到 users 時, users 表單必須已經建立,而且有 id 這個欄位。

這是資料庫層的限制,之後當寫入值的時候如果 user_id 為空或找不到任何 users 表單中的參照,會報錯。

可以進一步宣告當資料被刪除的話要如何處裡關聯的資料。

像是建立一個 user_settings 表單,當中的資料都會關連到 user

public function up()
{
    Schema::create('user_settings', function (Blueprint $table) {
        $table->uuid('id');

        $table->uuid('user_id');

        $table->foreign('user_id')->references('id')->on('users')
            ->onDelete('cascade');

        $table->timestamps();
    });
}

宣告 onDelete('cascade') 的話,當 user 被刪除時,跟他關聯的 user_setting 資料也會被刪除。

其他選項

onDelete('restrict'); //阻止刪除動作,除非 user_setting 被刪除不然 user 刪不了
onDelete('set null'); //刪除後 user_setting 的 user_id 為 null
                      //要將 user_id 設為 nullable 不然會報錯

也有 onUpdate,當關聯資料被更新時執行,選項跟 onDelete 一樣。

更名、刪除表單

可以更改既有的表單名稱

Schema::rename($from, $to);

或是刪除表單

Schema::drop('users'); //找不到表單的話會報錯

Schema::dropIfExists('users'); 

變更欄位

要變更現存的表單內的欄位的話,用 Schema::table()

public function up()
{
    Schema::table('user_settings', function (Blueprint $table) {
        //
    });
}

如果是新增欄位,做法跟建立表單時相同,直接宣告就好

Schema::table('user_settings', function (Blueprint $table) {
    $table->integer('height');
});

如果要變更現有的欄位,首先要安裝套件

sail composer require doctrine/dbal

然後用套件的 change 方法宣告是要變更現有欄位。

Schema::table('user_settings', function (Blueprint $table) {  
    $table->string('name', 50)->nullable()->change(); 
});

套件也有提供變更欄位名稱的方法

Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});

然後是刪除欄位的方法,這個也是套件的功能

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
    $table->dropColumn(['avatar', 'location']); //刪除多個
});

移除、變更外鍵關聯

想將之前設定的外鍵關聯移除的話

$table->dropForeign(['user_id']);

這樣只會將關聯性移除,欄位會保留

如果是要變更現有的關聯關係的話,只能先移除關聯後建立新的關聯

$table->dropForeign(['user_id']);
$table->foreign('user_id')->references('id')->on('users')
            ->onDelete('set_null');

刪除的話也是一樣

$table->dropForeign(['user_id']);
$table->dropColumn('user_id');

設定主鍵

一般會用 id 欄位作為主鍵值

$table->uuid('id')->primary();

若查詢時沒有指定欄位,就會以主鍵欄位做查詢。

也可以在宣告完所有欄位後再指定 primary 的欄位 ,閱讀上會比較容易辨識到主鍵的設定

$table->uuid('id');

...

$table->primary('id');

注意 primary 宣告必須在欄位被建立後才能執行,不然會報錯。

除了主鍵外有時也需要必須唯獨特值的欄位

$table->string('email')->unique();

跟 primary 一樣,也可以事後宣告

$table->string('email');
$table->unique('email');

另外偶爾會有需要聯合多個欄位為鍵值的設計

$table->primary(['account_id', 'created_at'],);
$table->unique(['account_id', 'created_at']); 

要移除鍵值的設定的話,以預設的鍵值名稱作為參照

$table->dropPrimary('users_id_primary'); //移除 users 表單中 id 的 primary 設定
$table->dropUnique('users_email_unique'); //移除 users 表單中 email 的 unique 鍵

Laravel 預設的鍵值名稱會以 "表單_欄位_屬性" 構成,如果不想要預設的名稱的話,可以在宣告鍵值時設定名稱

$table->primary(['account_id', 'created_at'],"account_created_at");

之後移除時以自訂的名稱作為參照

$table->dropPrimary('account_created_at');

執行 Migration

執行 migration 的基礎指令是

sail artisan migrate

這個指令會從最後一次 migration 的檔案之後開始執行 migration。

如果想退回 migration

sail artisan migrate:rollback //退回一個 migration
sail artisan migrate:rollback --step=5  //退回 5 個
sail artisan migrate:reset //退回全部 

如果想要從頭開始執行 migration

sail artisan migrate:refresh // 一步步退回全部後再執行全部
sail artisan migrate:fresh // 不執行退回而是直接刪除所有表單,再執行全部

如果在執行完 migration 後想要植入種子資料

sail artisan migrate --seed

開發上最常用的就是整個資料庫清空後重新建表單跟塞種子資料

sail artisan migrate:fresh --seed

References

Laravel Migrations
Laravel running migrations on "app/database/migrations" folder recursively

上一篇
資料庫連線設定
下一篇
Eloquent ORM - 建立 Model
系列文
Laravel 實務筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言